/*
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

package common;

import java.util.*;
import java.lang.*;

/**
 *лексический анализ json потока
 */
public class JsonScaner extends StringTable implements Iterable<String>
{
    private Iterator<String>	preparedIterator = null;
    private String		state		 = "";
    private String		pstate		 = "";
    private String		letter		 = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
    private String		digits		 = "1234567890";
    public String		error		 = "";

    private HashMap<String, String>	errDef = null;

    /**
     *конструяктор. просто конструяктор
     */
    public JsonScaner()
    {
	super();

	errDef = new HashMap<String, String>();
	
	addHead("letter", "digit", "{", "}", ":", ",", "\"", ".", "[", "]", " ", "+", "-");
	//heads      letter   0-9     {      }      :      ,     "       .      [      ]      \ 
	addRow("zro", "vaa", "dgs", "msy", "msy", "msy", "msy", "str", "err", "msy", "msy", "zro", "dgs", "dgs"); //zero state
	addRow("msy", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end"); //manage symbol
	addRow("str", "str", "str", "str", "str", "str", "str", "end", "str", "str", "str", "str", "str", "str");
	addRow("vaa", "vaa", "err", "err", "end", "err", "end", "err", "err", "err", "end", "end", "err", "err");
	addRow("dgs", "err", "dgs", "err", "end", "err", "end", "err", "dgf", "err", "end", "end", "err", "err"); //digit simple
	addRow("dgd", "err", "dgf", "err", "err", "err", "err", "err", "err", "err", "err", "err", "err", "err"); //decimal dot
	addRow("dgf", "err", "dgf", "err", "end", "err", "end", "err", "err", "err", "end", "end", "err", "err"); //digit float
	addRow("err", "e01", "e08", "e02", "e03", "e04", "e05", "e05", "e06", "e07", "e03", "e03", "e05", "e05");
	addRow("end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end", "end");

	errDef.put("e00", "ошибка в коде!");
	errDef.put("e01", "строка должна быть в кавычках");
	errDef.put("e02", "неожиданное начало объекта/пропущен идентификатор");
	errDef.put("e03", "дробное число без дробной части");
	errDef.put("e04", "неправильное описание пары 'ключ:значение'");
	errDef.put("e05", "недопустимый символ в числе");
	errDef.put("e06", "неожиданная точка");
	errDef.put("e07", "неожиданное начало массива");
	errDef.put("e07", "прерпределенные значения не могут содержать цифр");
    }

    /**
     *получить предыдущее состояние. т.к. по окончпнии сканирования состояние end
     *@return строковое представление состояния
     */
    public String getState()
    {
	return pstate;
    }

    /**
     *получить класс символа
     *@param sym символ подвергаемый анализу
     *@return класс символа
     */
    private String getSymClass(Character sym)
    {
	if(letter.indexOf(sym) >= 0)
	    {
		return "letter";
	    }
	else if (digits.indexOf(sym) >= 0)
	    {
		return "digit";
	    }
	return sym.toString();
    }

    /**
     *перевести КА в начальное состояние
     */
    public void setToBegin()
    {
	state = "zro";
    }

    /**
     *осуществить воздействие на КА.
     *@param sym символ алфавита
     *@return новое состояние
     */
    public String symbol(Character sym)
    {
	//int	foundEltIndex = search(getSymClass(sym));
	pstate		      = state;
	state		      = getValue(state, getSymClass(sym));

	if(state.equals("err") == true)
	    {
		error = errDef.get(getValue("err", getSymClass(sym)));
	    }

	//System.out.printf(">%s -> %s\n", pstate, state);
	return state;
    }

    /**
     *установить текст ошибки
     *@param atChar символ алфавита на котором произошла ошибка
     */
    private void setError(Character atChar)
    {
	error = errDef.get(getValue("err", atChar.toString()));
    }

    /**
     *реализация интерфейса iterable
     *@return возвращает итератор по токенам
     */
    public Iterator<String> iterator()
    {
	return preparedIterator;
    }

    /**
     *указание сканируемого объекта
     *@param src строка подлежащая сканированию
     */
    public void scan(String src)
    {
	preparedIterator = new JsonStringIterator(this, src);
    }

    /**
     *указание сканируемого объекта
     *@param reader объект настроенный на чтениие файла подлежащего сканированию
     */
    public void scan(Filer.Reader reader)
    {
	preparedIterator = new JsonFileIterator(this, reader);
    }

    /**
     *сканер json нотации в строковом представлении
     *реализован в виде итератора по токенам
     */
    public class JsonStringIterator implements Iterator<String>
    {
	protected JsonScaner	scaner = null;
	protected String	source = null; 
	protected int		begin  = 0;
	protected int		end    = 0;
	protected String	tmp    = null;

	/**
	 *конструяктор
	 *@param scn родительский объект
	 *@param src строка подлежащая сканированию
	 */
	public JsonStringIterator(JsonScaner scn, String src)
	    {
		scaner = scn;
		source = src;
	    }
	
	/**
	 *получить следующий токен
	 *@return возвращает следующий считанный токен. либо обрывает итерирование и выставляет ошибку
	 *следует проверить поле error объекта scaner дабы убедится что сканирование завершено без ошибок
	 */
	@Override public String next() throws NoSuchElementException
	{
	    String gSym;
	    scaner.setToBegin();
	    begin = end;
	    //System.out.println("src:"+source);
	    while(true)
		{
		    if(end >= source.length()) //достгли конца в процессе сканирования
			{
			    //грязный хак для файлового варианта
			    if(end == begin)
				{
				    if(hasNext() == false)
					{
					    throw new NoSuchElementException("end of stream");
					}
				    continue;
				}
			    else
				{
				    //надо прокрутить состояние для последнего символа 
				    //а то может  вернутся непральный тип токена
				    scaner.symbol(source.charAt(end-1));
				    return source.substring(begin);
				}
			}

		    gSym = scaner.symbol(source.charAt(end));
		    if(gSym.equals("end") == true)
			{
			    break;
			}

		    //наткнулись на игнорируемые символы
		    if(gSym.equals("zro") == true)
			{
			    end++;
			    begin = end;
			    continue;
			}

		    if(gSym.equals("err") == true)
			{
			    throw new JsonScanerException(scaner.error);
			}
		    end++;
		}

	    //надо сделать поправку на косую работу с 1 символьными строками
	    if(end - begin <= 0)
	    	{
		    end++;
		}
	    else if (scaner.getState().equals("str"))
		{
		    end++;
		}
	   
	    return source.substring(begin, end);
	}

	public boolean hasNext()
	{
	    if(end >= source.length())
		{
		    return false;
		}
	    return true;
	}

	public void remove()
	{
	    return; 
	}
	
    }

    public class JsonFileIterator extends JsonStringIterator
    {
	private  Filer.Reader	reader = null;

	public JsonFileIterator(JsonScaner scn, Filer.Reader rdr)
	    {
		super(scn, rdr.readLine());
		reader = rdr;
	    }

	@Override public boolean hasNext()
	{
	    if(end >= source.length())
		{
		    String tmp = reader.readLine();

		    if(tmp == null)
			{
			    return false;
			}

		    source = source.substring(begin)+tmp;
		    source = source.replaceAll("\n", " ");
		    end	   = end - begin;
		    begin  = 0;
		}

	    return true;
	}
    }
}